// BSD License (http://www.galagosearch.org/license)

package org.galagosearch.core.retrieval.structured;

import org.galagosearch.core.retrieval.structured.ScoringFunctionIterator;
import org.galagosearch.core.index.IndexReader.Iterator;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.TreeSet;
import java.util.StringTokenizer;

import org.galagosearch.core.index.IndexReader;
import org.galagosearch.core.index.PositionIndexReader;
import org.galagosearch.core.index.StructuredIndex;
import org.galagosearch.core.retrieval.query.Node;
import org.galagosearch.core.retrieval.query.StructuredQuery;
import org.galagosearch.core.retrieval.Retrieval;
import org.galagosearch.core.retrieval.ScoredDocument;
import org.galagosearch.core.retrieval.query.NodeType;
import org.galagosearch.core.retrieval.query.Traversal;
import org.galagosearch.tupleflow.Parameters;

import com.sun.org.apache.bcel.internal.generic.NEW;

/**
 *
 * @author trevor
 */
public class StructuredRetrieval extends Retrieval {
    StructuredIndex index;
    FeatureFactory featureFactory;
    
    HashMap<String, Integer> termNumberHash= new HashMap<String, Integer>(); 
    ArrayList<String> termList=new ArrayList<String>();
	ArrayList<ArrayList<Integer>> docList= new ArrayList<ArrayList<Integer>>();  
	ArrayList<ArrayList<Integer>> tfList= new ArrayList<ArrayList<Integer>>();
	ArrayList<ArrayList<Integer>> tbList= new ArrayList<ArrayList<Integer>>();
	ArrayList<ArrayList<Integer>> teList= new ArrayList<ArrayList<Integer>>();
    boolean tcAlreadyRead=false;
    String tcIndexFileName="";
    public StructuredRetrieval(StructuredIndex index, Parameters factoryParameters) {
        this.index = index;
        Parameters featureParameters = factoryParameters.clone();
        featureParameters.add("collectionLength", Long.toString(index.getCollectionLength()));
        featureParameters.add("documentCount", Long.toString(index.getDocumentCount()));
        featureFactory = new FeatureFactory(featureParameters);
        
    }
    

    public StructuredRetrieval(String filename, Parameters parameters)
            throws FileNotFoundException, IOException {
        this(new StructuredIndex(filename), parameters);
        tcIndexFileName=filename+"/parts/tcPostings";
    }

    public StructuredIndex getIndex() {
        return index;
    }

    public ScoredDocument[] getArrayResults(PriorityQueue<ScoredDocument> scores) {
        ScoredDocument[] results = new ScoredDocument[scores.size()];

        for (int i = scores.size() - 1; i >= 0; i--) {
            results[i] = scores.poll();
        }

        return results;
    }
    
    public NodeType getNodeType(Node node) throws Exception {
        NodeType nodeType = index.getNodeType(node);
        if (nodeType == null) {
            nodeType = featureFactory.getNodeType(node);
        }
        return nodeType;
    }
    
    public StructuredIterator createIterator(Node node) throws Exception {
        ArrayList<StructuredIterator> internalIterators = new ArrayList<StructuredIterator>();

        for (Node internalNode : node.getInternalNodes()) {
            StructuredIterator internalIterator = createIterator(internalNode);
            internalIterators.add(internalIterator);
        }
        
        StructuredIterator iterator = index.getIterator(node);
        if (iterator == null) {
            iterator = featureFactory.getIterator(node, internalIterators);
        }
        
        return iterator;
    }

    public Node transformQuery(Node queryTree) throws Exception {
        List<Traversal> traversals = featureFactory.getTraversals(this);
        for (Traversal traversal : traversals) {
            queryTree = StructuredQuery.copy(traversal, queryTree);
        }
        return queryTree;
    }

    /**
     * Evaluates a query.
     *
     * @param queryTree A query tree that has been already transformed with StructuredRetrieval.transformQuery.
     * @param requested The number of documents to retrieve, at most.
     * @return
     * @throws java.lang.Exception
     */
    public ScoredDocument[] runQuery(Node queryTree, int requested) throws Exception {
        // construct the query iterators
        ScoreIterator iterator = (ScoreIterator) createIterator(queryTree);

        // now there should be an iterator at the root of this tree
        PriorityQueue<ScoredDocument> queue = new PriorityQueue<ScoredDocument>();

        while (!iterator.isDone()) {
            int document = iterator.nextCandidate();
            int length = index.getLength(document);
            double score = iterator.score(document, length);

            if (queue.size() <= requested || queue.peek().score < score) {
                ScoredDocument scoredDocument = new ScoredDocument(document, score);
                queue.add(scoredDocument);

                if (queue.size() > requested) {
                    queue.poll();
                }
            }

            iterator.movePast(document);
        }

        return getArrayResults(queue);
    }
    public ScoredDocument[] runQueryTC(Node queryTree, int requested) throws Exception {
        // construct the query iterators
        if(!tcAlreadyRead) readTCIndex();
        tcAlreadyRead=true;
    	ScoreIterator iterator = (ScoreIterator) createIterator(queryTree);

        // now there should be an iterator at the root of this tree
        PriorityQueue<ScoredDocument> queue = new PriorityQueue<ScoredDocument>();
        ScoreCombinationIterator sci= (ScoreCombinationIterator)iterator;
        
       
        
       // IndexReader.VocabularyBlock vb= (IndexReader.VocabularyBlock)iri.block;
        //int numberOfTerms= vb.keys.length;
        
        int queryTermNumber=0;
        HashMap<Integer,Double> docIDScoreHash= new HashMap<Integer,Double>();
        
        HashMap<Integer,Integer> docIDtbHash = new HashMap<Integer, Integer>();
        HashMap<Integer,Integer> docIDteHash = new HashMap<Integer, Integer>();
        
        for(ScoreIterator si: ((ScoreCombinationIterator)iterator).iterators)
        {
        	ScoringFunctionIterator sfi= (ScoringFunctionIterator)sci.iterators[queryTermNumber++];
        	//System.out.println("isDone: "+sfi.isDone());
        	if(sfi.isDone()==true){
        		//return getArrayResults(queue);
        		continue;
        	}
        	//System.out.println("Coming here");
            PositionIndexReader.Iterator pir= (PositionIndexReader.Iterator)sfi.iterator;
            IndexReader.Iterator iri= (IndexReader.Iterator) pir.iterator;
            String term=iri.getKey();
        	
            int termIndex=termNumberHash.get(term);
            termIndex=termIndex-1;
            //now get all the docs that contain given term
            ArrayList<Integer> currDocs=docList.get(termIndex);
            ArrayList<Integer> currTFs=tfList.get(termIndex);
            ArrayList<Integer> currTBs=tbList.get(termIndex);
            ArrayList<Integer> currTEs=teList.get(termIndex);
            int postingLength=currDocs.size();
           // System.out.println("TotalDocs:"+postingLength);
            for(int i=0;i<postingLength;i++)
            {
            	int document=currDocs.get(i);
            	int tf=currTFs.get(i);
            	int tb=currTBs.get(i);
            	int te=currTEs.get(i);
            	 
            	int currInputTime = stringInputTimetoIntInputTime(ipTime);

            	if(checkDocumentValidity(currInputTime,tb,te) )
            	{
            		int tbNew = (docIDtbHash.get(document)==null)?tb:((docIDtbHash.get(document)>tb)?docIDtbHash.get(document):tb);
            		int teNew = (docIDteHash.get(document)==null)?te:((docIDteHash.get(document)<te)?docIDteHash.get(document):te);
            	
            		docIDtbHash.put(document,tbNew);
            		docIDteHash.put(document,teNew);
            		double score=si.score(document, index.getLength(document), tf);
                	double prevScore=(docIDScoreHash.get(document)==null)?0:docIDScoreHash.get(document);
                	docIDScoreHash.put(document, score+prevScore);
            	}
            	
            }
        	
        }
        //put documents in descending order of scores
        //HashMap<Integer,Double> sorted=sortHashMap(docIDScoreHash);
        HashMap<Integer,Double> sorted=docIDScoreHash;
        
        java.util.Iterator<Integer> docIterator = sorted.keySet().iterator();
        for(int i=0;i<sorted.size();i++){
        	int currDocID = docIterator.next();
        	//System.out.println("DocID: "+currDocID+"\t Score: "+sorted.get(currDocID));
        	double currScore = sorted.get(currDocID);
        	int currTimeBegin = docIDtbHash.get(currDocID);
        	int currTimeEnd = docIDteHash.get(currDocID);
        	//create queue
        	if (queue.size() <= requested || queue.peek().score < currScore) {
                ScoredDocument scoredDocument = new ScoredDocument(currDocID, currScore,currTimeBegin,currTimeEnd);  
                queue.add(scoredDocument);

                if (queue.size() > requested) {
                    queue.poll();
                }
            }
        }
        
        /*while (!iterator.isDone()) {
            int document = iterator.nextCandidate();
            int length = index.getLength(document);
            int tf=0;// this we have to get
            
            
            double score = iterator.score(document, length);

            if (queue.size() <= requested || queue.peek().score < score) {
                ScoredDocument scoredDocument = new ScoredDocument(document, score);
                queue.add(scoredDocument);

                if (queue.size() > requested) {
                    queue.poll();
                }
            }

            iterator.movePast(document);
        }*/
        //System.out.println("Q size:"+queue.size());
        if(queue==null)
        	System.out.println("Throwing empty queue error ! ");
        return getArrayResults(queue);
    }
    
    public boolean checkDocumentValidity(int inputTime, int timeBegin, int timeEnd){

		boolean isValid = false;

		if(timeEnd==-1){
			//int timeE = -1;
			if(timeBegin<=inputTime)
				isValid = true;
		}
		else{
			if(timeBegin<=inputTime && timeEnd>=inputTime)
				isValid = true;
		}	
		return isValid;
	}
	private int stringInputTimetoIntInputTime(String inputTime) {
		// TODO Auto-generated method stub
		StringTokenizer str = new StringTokenizer(inputTime,"-");
		
		String s1=(String) str.nextElement();
		String s2=(String) str.nextElement();
		String s3=(String) str.nextElement();
		
		if(s2.length()==1)
			s2 = "0"+s2;
		
		String outputString = s3.substring(2, 4) + s2 + s1 + "0000";
	//	System.out.println("year = "+s3.substring(2, 4)+"month = "+s2+"Date: "+s1+"Output: "+outputString);
		return Integer.parseInt(outputString);
	}

	    
    private HashMap<Integer,Double> sortHashMap(HashMap<Integer, Double> input){
        Map<Integer, Double> tempMap = new HashMap<Integer, Double>();
        int[] ordering=new int[input.keySet().size()];
        for (Integer wsState : input.keySet()){
            tempMap.put(wsState,input.get(wsState));
        }

        List<Integer> mapKeys = new ArrayList<Integer>(tempMap.keySet());
        List<Double> mapValues = new ArrayList<Double>(tempMap.values());
        HashMap<Integer, Double> sortedMap = new LinkedHashMap<Integer, Double>();
        TreeSet<Double> sortedSet = new TreeSet<Double>(mapValues);
        Object[] sortedArray = sortedSet.toArray();
        int size = sortedArray.length;
        for (int i=size-1; i>-1; i--){
        	ordering[i]=mapValues.indexOf(sortedArray[i]);
            sortedMap.put(mapKeys.get(mapValues.indexOf(sortedArray[i])), 
                          (Double)sortedArray[i]);
        }
        return sortedMap;
    }
    private void readTCIndex() throws Exception {
		// TODO Auto-generated method stub
    	FileReader fr= new FileReader(tcIndexFileName);
    	String term="";
    	String tempRest="";
    	
    	int termNo=0;
    	while(fr!=null )
    	{
    		termNumberHash.put(term,termNo++);
    		//System.out.println(++termNo);
    		//if(termNo==927)
    		//{
    		// System.out.println("break here");
    		//}
    		term=readTill(fr,':');
    		if(term==null) break;
    		termList.add(term);
    		 
    		tempRest=readTill(fr,'\n');
    		ArrayList<Integer> d=new ArrayList<Integer>();
    		ArrayList<Integer> f=new ArrayList<Integer>();
    		ArrayList<Integer> tbPost=new ArrayList<Integer>();
    		ArrayList<Integer> tePost=new ArrayList<Integer>();
    		
    		while(tempRest.compareTo("")!=0)
    		{
    			int start=tempRest.indexOf('[');
    			int stop=tempRest.indexOf(']');
    			if (start==-1) 
    			{
    					tempRest="";
    					continue;
    			}
    			String a=tempRest.substring(start,stop);
    			String b=tempRest.substring(stop+1);
    			
    			int demark=a.indexOf(',');  
    			String docID=a.substring(1,demark);
    			//System.out.println(docID);
    			a=a.substring(demark+1);
    			
    			demark=a.indexOf(',');  
    			String tf=a.substring(0,demark);
    			//System.out.println(tf);
    			a=a.substring(demark+1);
    			
    			demark=a.indexOf(',');  
    			String tb=a.substring(0,demark);
    			//System.out.println(tb);
    			a=a.substring(demark+1);
    			
    			//demark=a.indexOf('');  
    			String te=a;//.substring(0,demark);
    			//System.out.println(te);
    			
    			
    			int intDoc=Integer.parseInt(docID); d.add(intDoc);
    			int intTF=Integer.parseInt(tf); f.add(intTF);
    			int intTB=Integer.parseInt(tb); tbPost.add(intTB);
    			int intTE=Integer.parseInt(te); tePost.add(intTE);
    			
    			tempRest=b;
    		}
    		docList.add(d);
    		tfList.add(f);
    		tbList.add(tbPost);
    		teList.add(tePost);
    		//System.out.print(".");
    	}
    	
    	//System.out.println("Finished Reading Index");
		
	}

    public String readTill(FileReader fr, char d) throws Exception
    {
    	String op="";
    	char a=(char)fr.read();
    	if(a==65535) 
    	{
    		tcAlreadyRead=true;
    		return null;
    	}
    	while(a!=d && a!=65535)
    	{
    		op=op+a;
    		//System.out.println("+");
    		a=(char)fr.read();
    		if((int)a==65535)
    		{
        		tcAlreadyRead=true;
        		break;
    		}
    	}
    	return op;
    }
    
	public String getDocumentName(int document) {
        return index.getDocumentName(document);
    }

    public void close() throws IOException {
        index.close();
    }
}
